package ICM.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;

import common.*;
import ocsf.server.*;
import ICM.client.Client;
import ICM.server.ServerUI;
import ICM.server.UserManager;

public class Server extends ObservableServer {
	
	/**
	 * Default listening port
	 */
	public static final int DEFAULT_PORT=5555;
	private ConnectionToClient client = null;
	
	public static ArrayList<ConnectedClient> ConnectedUsers = new ArrayList<ConnectedClient>();;
	
	/**
	 * Constructor
	 * @param port
	 */
	public Server(int port) {
		super(port);
	}
	
	/**
	 * Received message handler
	 */
	protected synchronized void handleMessageFromClient(Object message, ConnectionToClient client) {
		this.client = client;
		setChanged();
		if (message instanceof CommandMessage) {
			notifyObservers(((CommandMessage)message).ToDebugMessage());
			// Handle message
			try {
				client.sendToClient(ParseCommandMessage((CommandMessage)message));
			} catch (IOException e) {
				notifyObservers("Can't send message to client: " + client.getInetAddress().toString());
			}
		}
		else
			notifyObservers(message);
	}
	
	/**
	 * Parse received command message and generate reply message
	 * @param message Received command message and type
	 * @return Reply message
	 */
	private AbstractMessage ParseCommandMessage(CommandMessage txt) {
		/**
		 * If the User Object then the login is correct and sends a message
		 * with the User Object to the client, otherwise sends a message with NULL which means
		 * that the login is incorrect.
		 */
		if(txt.getCommand().equals(CommandMessage.LOGIN_MESSAGE)) {
			//Check Login and reply
			User u=UserManager.Login(Integer.parseInt((String)txt.getParams()[0]),(String)txt.getParams()[1]);
			String message = CommandMessage.LOGIN_MESSAGE;
			//If the user's login is correct, add the user to the connected users array
			if (u!=null)
			{
				for (int i=0;i<ConnectedUsers.size();i++)
				{
					if (ConnectedUsers.get(i).getUserID()==u.getId())
					{
						message = "User already connected";
						break;
					}
				}
				if (message.equals(CommandMessage.LOGIN_MESSAGE))
					ConnectedUsers.add(new ConnectedClient(u.getId(),client));
			}

			return new CommandMessage(CommandMessage.ACKNOWLEDGE_MESSAGE,new Object[] {message,u });
		}
		/**
		 * Remove user from connected users array
		 */
		if(txt.getCommand().equals(CommandMessage.LOGOUT_USER)) {
			int UserID=(Integer)txt.getParams()[0];
			String message = CommandMessage.LOGOUT_USER;
				for (int i=0;i<ConnectedUsers.size();i++)
					if (ConnectedUsers.get(i).getUserID()==UserID)
					{
						ConnectedUsers.remove(i);
						return new CommandMessage("Logout response",new Object[] {message,true});
					}
				return new CommandMessage("Logout response",new Object[] {message,false});
		}
		/**
		 * Get all active requests for user that logged in 
		 */
		else if(txt.getCommand().equals("Get User requests")) {
			 Hashtable<Integer,String> rs = UserManager.UserRequests((Integer)txt.getParams()[0]);
			return new CommandMessage("UserRequests",new Object[] {"UserRequests", rs});
		}
		/**
		 * Get information about stage of specific request for monitoring request
		 */
		else if(txt.getCommand().equals("Get request stage")) {
			String[] myIntArray = UserManager.RequestInfo((Integer)txt.getParams()[0],(Integer)txt.getParams()[1]);
			return new CommandMessage("RequestInfo",new Object[] {"RequestInfo", myIntArray});
		}
		/**
		 * get all requests in which  specific engineer has role
		 */
		else if(txt.getCommand().equals("Get engineer requests")) {
			ArrayList<String[]> rs= UserManager.ResponsibleRequest((Integer)txt.getParams()[0]);
			return new CommandMessage("Role In Request",new Object[] {"Role In Request", rs});
		}
		/**
		 * Returns info about stages Meaningful assessment and Performance to Supervisor  
		 */
		else if(txt.getCommand().equals("SupervisorApp")) {
			ArrayList<String[]> rs= UserManager.getStagesForAppointment((Integer)txt.getParams()[0]);
			return new CommandMessage("Appointment stage",new Object[] {"Appointment stage", rs});
		}
		/**
		 * Get all the new and active requests
		 */
		else if(txt.getCommand().equals("Get new and active requests")) {
			Request[] requests = UserManager.getNewRequests();
			return new CommandMessage("All new and active requests",new Object[] {"All new and active requests", requests});
		}
		/**
		 * Get all requests
		 */
		else if(txt.getCommand().equals("Get requests")) {
			Request[] requests = UserManager.getRequests();
			return new CommandMessage("All requests",new Object[] {"All requests", requests});
		}
		/**
		 * Get all users
		 */
		else if(txt.getCommand().equals("Get Users")) {
			//Get users
			User[] users = UserManager.getUsers();
			return new CommandMessage("All Users",new Object[] {"All Users", users});
		}
		/**
		 * Get stage name given the stage ID
		 */
		else if(txt.getCommand().equals("Get stage name")) {
			//Get stage name
			String StageName = UserManager.getStageName((Integer)txt.getParams()[0]);
			return new CommandMessage("Stage name",new Object[] {"Stage name", StageName});
		}
		/**
		 * Assign or change a Supervisor for a specific request
		 */
		else if(txt.getCommand().equals("Change supervisor")) {
			//Change supervisor
			boolean ChangeSupervisor = UserManager.ChangeSupervisor(Integer.parseInt((String)txt.getParams()[0]),Integer.parseInt((String)txt.getParams()[1]));
			return new CommandMessage("Supervisor changed",new Object[] {"Supervisor changed", ChangeSupervisor});
		}
		/**
		 * Change a committee member given the details of the old committee member and the new committee member
		 */
		else if(txt.getCommand().equals("Change committee member")) {
			//Change supervisor
			boolean ChangeCommitteeMember = UserManager.ChangeCommitteeMember(Integer.parseInt((String)txt.getParams()[0]),Integer.parseInt((String)txt.getParams()[1]));
			return new CommandMessage("Committee member changed",new Object[] {"Committee member changed", ChangeCommitteeMember});
		}
		/**
		 * Assign a new committee member
		 */
		else if(txt.getCommand().equals("New committee member")) {
			//Change supervisor
			boolean NewCommitteeMember = UserManager.NewCommitteeMember(Integer.parseInt((String)txt.getParams()[0]),(String)txt.getParams()[1]);
			return new CommandMessage("Committee member added",new Object[] {"Committee member added", NewCommitteeMember});
		}
		/**
		 * Get all employees
		 */
		else if(txt.getCommand().equals("Get employees")) {
			//Get employees
			User[] employees = UserManager.getEmployees();
			return new CommandMessage("All employees",new Object[] {"All employees", employees});
		}
		/**
		 * Get all system engineers
		 */
		else if(txt.getCommand().equals("Get system engineers")) {
			//Get engineers
			SystemEngineer[] syseng = UserManager.getSystemEngineers();
			return new CommandMessage("All system engineers",new Object[] {"All system engineers", syseng});
		}
		/**
		 * Get all committee members
		 */
		else if(txt.getCommand().equals("Get committee members")) {
			//Get committee members
			User[] committee = UserManager.getCommittee();
			return new CommandMessage("Committee members",new Object[] {"Committee members", committee});
		}
		/**
		 * Add a new request
		 */
		else if(txt.getCommand().equals("Add request")) {
			boolean NewCommitteeMember = UserManager.AddRequest((String)txt.getParams()[0],Integer.parseInt((String)txt.getParams()[1]),(String)txt.getParams()[2],(String)txt.getParams()[3],(String)txt.getParams()[4],(FileUpload)txt.getParams()[5]);
			return new CommandMessage("Request added",new Object[] {"Request added", NewCommitteeMember});
		}
		/**
		 * Get a specific engineer requests
		 */
		else if(txt.getCommand().equalsIgnoreCase("Get request data")){
			Request request=UserManager.RequestInfo((Integer)txt.getParams()[0]);
			return new CommandMessage("request data",new Object[] {"request data", request});
		}
		else if(txt.getCommand().equalsIgnoreCase("Review and decision to last stage")){
			boolean stage=UserManager.BackStageRW(((Integer)txt.getParams()[0]));
			return new CommandMessage("back",new Object[] {"back", stage});
		}
		else if(txt.getCommand().equalsIgnoreCase("Testing to last stage")){
			boolean stage=UserManager.BackStageT(((Integer)txt.getParams()[0]));
			return new CommandMessage("back",new Object[] {"back", stage});
		}
		/**
		 * Save assessor report in database
		 */
		else if(txt.getCommand().equalsIgnoreCase("Add Assessor Report")){
			boolean NewAssessoReport = UserManager.AddAssessorReport(
					(Integer)txt.getParams()[0]
					,(String)txt.getParams()[1]
					,(String)txt.getParams()[2]
					,(String)txt.getParams()[3]
					,(String)txt.getParams()[4]);
			return new CommandMessage("Assessor report added",new Object[] {"Assessor report added", NewAssessoReport});
		}
		else if(txt.getCommand().equalsIgnoreCase("Add Assessor duration")){
			boolean NewAssessoReport = UserManager.AddAssessorDuration((Integer)txt.getParams()[0],(String)txt.getParams()[1],(Integer)txt.getParams()[2]);
			return new CommandMessage("Assessor duration added",new Object[] {"Assessor duration added", NewAssessoReport});
		}
		/**
		 * Generate a new periodic report and send it to the manager
		 */
		else if(txt.getCommand().equals("Get periodic report")) {
			Report PeriodicReport = UserManager.getPeriodicReports();
			return new CommandMessage("Periodic report",new Object[] {"Periodic report", PeriodicReport});
		}
		/**
		 * Commit second stage
		 */
		else if(txt.getCommand().equals("Commit decision stage")) {
			boolean comm = UserManager.CommitStage((Integer)txt.getParams()[0]);
			return new CommandMessage("Commit",new Object[] {"Commit", comm});
		}
		/**
		 * Commit fourth stage
		 */
		else if(txt.getCommand().equals("Commit testing stage")) {
			boolean comm = UserManager.CommitStage2((Integer)txt.getParams()[0]);
			return new CommandMessage("Commit",new Object[] {"Commit", comm});
		}
		/**
		 * Reject second Stage
		 */
		else if(txt.getCommand().equals("Reject decision stage")) {
			boolean rej = UserManager.RejectStage((Integer)txt.getParams()[0]);
			return new CommandMessage("Reject",new Object[] {"Reject", rej});
		}
		/**
		 * Change specific data in a stage
		 */
		else if(txt.getCommand().equals("Change data")) {
			boolean Data = UserManager.setStageData(Integer.parseInt((String)txt.getParams()[0]),(String)txt.getParams()[1],(String)txt.getParams()[2],(String)txt.getParams()[3],(String)txt.getParams()[4],(String)txt.getParams()[5]);
			return new CommandMessage("Data",new Object[] {"Data", Data});
			}
		/**
		 * Get all generated periodic reports
		 */
		else if(txt.getCommand().equals("Get all periodic reports")) {
			Report[] AllPeriodicReports = UserManager.getAllPeriodicReports();
			return new CommandMessage("All periodic reports",new Object[] {"All periodic reports", AllPeriodicReports});
		}
		/**
		 * Get a performances report
		 */
		else if(txt.getCommand().equals("Get performances")) {
			int[] Performances = UserManager.getPerformances();
			return new CommandMessage("Performances",new Object[] {"Performances", Performances});
		}
		/**
		 * Get stage data
		 */
		else if(txt.getCommand().equals("Get stage data")) {
			String[] stage = UserManager.getStageData((Integer)txt.getParams()[0],(Integer)txt.getParams()[1]);
			return new CommandMessage("Stage data",new Object[] {"Stage data", stage});
		}
		/**
		 * Set stage duration
		 */
		else if(txt.getCommand().equals("Set stage duration")) {
			boolean DurationChange = UserManager.setStageDuration((Integer)txt.getParams()[0],(Integer)txt.getParams()[1],(Integer)txt.getParams()[2]);
			return new CommandMessage("Stage duration",new Object[] {"Stage duration", DurationChange});
		}
		/**
		 * Set stage report
		 */
		else if(txt.getCommand().equals("Set stage report")) {
			boolean StageReportChange = UserManager.setStageReport(Integer.parseInt(txt.getParams()[0].toString()),Integer.parseInt(txt.getParams()[1].toString()),(String)txt.getParams()[2]);
			return new CommandMessage("Stage report",new Object[] {"Stage report", StageReportChange});
		}
		/**
		 * Get median and standard deviation for delays durations
		 */
		else if(txt.getCommand().equals("Get median and standard deviation for delays durations")) {
			double[] MedianAndStdDev1 = null;
			if (txt.getParams()==null)
				MedianAndStdDev1 = UserManager.getMedianAndStdDev("SELECT requestID,SUM(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionDays FROM stage GROUP BY requestID HAVING OverallExceptionDays IS NOT NULL;");
			else
				MedianAndStdDev1 = UserManager.getMedianAndStdDev("SELECT requestID,SUM(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionDays FROM stage,requests WHERE idRequests=requestID AND Department='"+(String)txt.getParams()[0]+"' GROUP BY requestID HAVING OverallExceptionDays IS NOT NULL;");
			return new CommandMessage("Median and standard deviation for delays durations",new Object[] {"Median and standard deviation for delays durations", MedianAndStdDev1});
		}
		/**
		 * Get frequency distribution for delays durations
		 */
		else if(txt.getCommand().equals("Get frequency distribution for delays durations")) {
			FreqDistr[] FreqDistr1 = null;
			if (txt.getParams()==null)
				FreqDistr1 = UserManager.getFreqDistr("SELECT OverallExceptionDays,COUNT(*) FROM (SELECT requestID,SUM(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionDays FROM stage GROUP BY requestID HAVING OverallExceptionDays IS NOT NULL) AS temp GROUP BY OverallExceptionDays;");
			else
				FreqDistr1 = UserManager.getFreqDistr("SELECT OverallExceptionDays,COUNT(*) FROM (SELECT requestID,SUM(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionDays FROM stage,requests WHERE idRequests=requestID AND Department='"+(String)txt.getParams()[0]+"' GROUP BY requestID HAVING OverallExceptionDays IS NOT NULL) AS temp GROUP BY OverallExceptionDays;");
			return new CommandMessage("Frequency distribution for delays durations",new Object[] {"Frequency distribution for delays durations", FreqDistr1});
		}
		/**
		 * Get median and standard deviation for number of delays
		 */
		else if(txt.getCommand().equals("Get median and standard deviation for number of delays")) {
			double[] MedianAndStdDev2 = null;
			if (txt.getParams()==null)
				MedianAndStdDev2 = UserManager.getMedianAndStdDev("SELECT requestID,COUNT(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionCount FROM stage GROUP BY requestID HAVING OverallExceptionCount IS NOT NULL;");
			else
				MedianAndStdDev2 = UserManager.getMedianAndStdDev("SELECT requestID,COUNT(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionCount FROM stage,requests WHERE idRequests=requestID AND Department='"+(String)txt.getParams()[0]+"' GROUP BY requestID HAVING OverallExceptionCount IS NOT NULL;");
			return new CommandMessage("Median and standard deviation for number of delays",new Object[] {"Median and standard deviation for number of delays", MedianAndStdDev2});
		}
		/**
		 * Get frequency distribution for number of delays
		 */
		else if(txt.getCommand().equals("Get frequency distribution for number of delays")) {
			FreqDistr[] FreqDistr2 = null;
			if (txt.getParams()==null)
				FreqDistr2 = UserManager.getFreqDistr("SELECT OverallExceptionCount,COUNT(*) FROM (SELECT requestID,COUNT(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionCount FROM stage GROUP BY requestID HAVING OverallExceptionCount IS NOT NULL) AS temp GROUP BY OverallExceptionCount;");
			else
				FreqDistr2 = UserManager.getFreqDistr("SELECT OverallExceptionCount,COUNT(*) FROM (SELECT requestID,COUNT(DATEDIFF(ExceptionEndDate,ExceptionStartDate)) AS OverallExceptionCount FROM stage,requests WHERE idRequests=requestID AND Department='"+(String)txt.getParams()[0]+"' GROUP BY requestID HAVING OverallExceptionCount IS NOT NULL) AS temp GROUP BY OverallExceptionCount;");
			return new CommandMessage("Frequency distribution for number of delays",new Object[] {"Frequency distribution for number of delays", FreqDistr2});
		}
		else if(txt.getCommand().equals("Get assessor report data")){
			String[] st=UserManager.getAssessorReportData((Integer)txt.getParams()[0]);
			return new CommandMessage("report data",new Object[] {"report data",st});
		}

		else if(txt.getCommand().equals("AssessorCommit")){
			boolean ACommit = UserManager.AssessorCommit((Integer)txt.getParams()[0]);
			return new CommandMessage("CommitOfMaingfulAssessment",new Object[] {"CommitOfMaingfulAssessment", ACommit});
		}

		else if(txt.getCommand().equals("Send email")){
			boolean EmailSent = UserManager.sendMail((String)txt.getParams()[0],(String)txt.getParams()[1]);
			return new CommandMessage("Email sent",new Object[] {"Email sent",EmailSent});
		}
		else if(txt.getCommand().equals("Close request")){
			boolean CloseRequest = UserManager.CloseRequest(Integer.parseInt((String)txt.getParams()[0]),(String)txt.getParams()[1]);
			return new CommandMessage("Request closed",new Object[] {"Request closed",CloseRequest});
		}
		else if(txt.getCommand().equals("Finish execution stage")){
			boolean StageFinished = UserManager.FinishExeStage(Integer.parseInt((String)txt.getParams()[0]));
			return new CommandMessage("Stage finished",new Object[] {"Stage finished",StageFinished});
		}
		else if(txt.getCommand().equals("Finish assessment stage")){
			boolean StageFinished = UserManager.FinishAssessStage(Integer.parseInt((String)txt.getParams()[0]));
			return new CommandMessage("Stage finished",new Object[] {"Stage finished",StageFinished});
		}


		else
			return new TextMessage("Error");
		
	
		
	}

	/**
	 * Entry point 
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
		// Create two objects: Server, ServerUI
		Server s = new Server(DEFAULT_PORT);
		ServerUI ui = new ServerUI();
		// Add observer to server
		s.addObserver(ui);
		// Show UI
		ui.setVisible(true);
		
		// Start listening
		try {
			s.listen();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		DBManager db = DBManager.getInstance();
		BackgroundProcess process = new BackgroundProcess(db.getConnection());
		process.start();
		
		CheckUser process2 = new CheckUser();
		process2.start();
	}

}

/**
 * This class is a structure the saves the details of the current user: the user ID and 
 * the client connection details.
 */
class ConnectedClient
{
	private ConnectionToClient client;
	private int UserID;
	
	public ConnectedClient(int UserID, ConnectionToClient client)
	{
		this.UserID = UserID;
		this.client = client;
	}
	
	public void setClient(ConnectionToClient client)
	{
		this.client=client;
	}
	public ConnectionToClient getClient()
	{
		return client;
	}
	
	public void setUserID(int UserID)
	{
		this.UserID=UserID;
	}
	public int getUserID()
	{
		return UserID;
	}
}
